/*
 *  Copyright 2011 yura.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

package org.opu.vcforms.transform_t2d.content;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import org.opu.vcforms.datasource.DataReceiver;
import org.opu.vcforms.datasource.DbQueryHandler;
import org.opu.vcforms.datasource.LocalStorege;
import org.opu.vcforms.datasource.Storage;
import org.opu.vcforms.template.bean.Content;
import org.opu.vcforms.transform_t2d.MainProcessor;
import static org.opu.vcforms.transform_t2d.Logger.*;

/**
 *
 *  Content is specific element, in template it can be, but in
 * result document - can`t. So the task of processor get all data
 * from content and insert it to document. Also content can have
 * variable thar get regular data from it, or can control it.
 *
 * Content is specific structure that don`t needed in result document,
 * so this class dont implemented transformation.
 *
 * @author yura
 */
public class ContentProcessor implements ContentAPI{

    private MainProcessor transformer;
    private Content content;
    private ContentAPI parent;

    /** Curent storage */
    private Storage storage;
    /** Global iterator */
    private Iterator<LocalStorege> iterator;
    
    /** Map keys of current local-storage (null at the begining) */
    private LocalStorege next;
    /** Iterator of map keys of current local-storage (null at the begining) */
    private Iterator<String> mapIterator;

    /** Last value key */
    private String lastId;
    /** Last value */
    private Object lastVal;
    /** Iteration, 0 in the beginning, so after the first step was 1 */
    private int iteration = 0;

    public ContentProcessor(ContentAPI parent, MainProcessor transformer, Content content) {
        this.transformer = transformer;
        this.content = content;
        this.parent = parent;
        out(var(SPACE) + "Content.<created>");
    }

    public Content getContent() {
        return content;
    }

    public void setContent(Content content) {
        this.content = content;
    }

    public MainProcessor getTransformer() {
        return transformer;
    }

    public void setTransformer(MainProcessor transformer) {
        this.transformer = transformer;
    }

    public void setParent(ContentAPI parent) {
        this.parent = parent;
    }

    /**
     *  Init {@link LocalStorege}. If it data
     * @throws NullPointerException <ol>
     * <li>content null</li>
     * <li>sql return null</li>
     * <li>unknown content type</li>
     * </ol>
     * @throws IllegalArgumentException if problems with parsing sql query.
     */
    public void init() throws NullPointerException, IllegalArgumentException{
        out(var(SPACE) + "Content.<init>");
        if (content == null){
            throw new NullPointerException("Content can be null");
        }
        out(var(SPACE) + "Content.<init>: Content type is " + content.getType());

        // if we work with database.
        if (Content.TYPE_DATABASE.equals(content.getType())){
            String get = content.get(Content.PROPERYY_SQL);
            DataReceiver dbManager = transformer.getDataReceiver();
            DbQueryHandler dqh = new DbQueryHandler(dbManager);

            ContentSqlPreparator preparator = new ContentSqlPreparator(this);
            String parsedQuery = preparator.parseQuery(get);
            storage = new Storage(dqh.get(parsedQuery));

            out(var(SPACE) + "Content.<init>: Sql: " + parsedQuery.replaceAll(
                    "\n", "\n" + var(SPACE) + "                  #: "));
        }

        // if we work with user settings
        else if (Content.TYPE_USER_SETTINGS.equals(content.getType())){
            String get = content.get(Content.PROPERYY_SETTINGS_TYPE);

            storage = transformer.getUserSettings().get(get);

            out(var(SPACE) + "Content.<init>: Inject - " + get);
        }

        // if we work with settings - it`s perversion, but user always right
        else if (Content.TYPE_SYSTEM_SETTINGS.equals(content.getType())){
            storage = new Storage(toMaps(transformer.getSystemSettings()));

            out(var(SPACE) + "Content.<init>: Properties - "
                    + transformer.getSystemSettings());
        }

        // else it can be processed.
        else {
            throw new NullPointerException("Unknown content type - '"
                    + content.getType() + "'");
        }

        iterator = storage.iterator();
        step();
    }

    /**
     *  Create collection with wan element.
     * @param properties data of collection
     * @return collection with wan element.
     */
    public Collection<Map> toMaps(Properties properties){
        return new ArrayList<Map>(Arrays.asList(properties));
    }

    @Override
    public String get(String name) {
        out(var(SPACE) + "Content.get (" + name + ")");

        String ret = null;
        if (next != null){
            Object get = next.get(name);

            if (get != null){
                ret = lastId = String.valueOf(get);
            }
        }
        
        out(var(SPACE) + "Content.get: return - " + ret);

        return ret;
    }

    @Override
    public String next() {
        out(var(SPACE) + "Content.next");

        if (mapIterator != null){
            lastId = mapIterator.next();
            out(var(SPACE) + "Content.next: lastId - " + lastId);
            
            lastVal = next.get(lastId);
            out(var(SPACE) + "Content.next: lastVal - " + lastVal);

            out(var(SPACE) + "Content.next: return - " + lastVal);

            return String.valueOf(lastVal);
        }

        out(var(SPACE) + "Content.next: return - NULL");
        return null;
    }

    @Override
    public boolean hasNext() {
        out(var(SPACE) + "Content.hasNext");

        boolean ret = false;
        if (mapIterator != null){
            ret = mapIterator.hasNext();
        }

        out(var(SPACE) + "Content.hasNext: return - " + ret);
        return ret;
    }

    @Override
    public boolean step() {
        out(var(SPACE) + "Content.step");

        try {
            next = iterator.next();
        } catch (NoSuchElementException e) {
            next = null;
        }
        out(var(SPACE) + "Content.step: next storage - " + next);
        
        iteration ++;
        out(var(SPACE) + "Content.step: iteration - " + iteration);

        if (next != null){
            mapIterator = next.iterator();
            out(var(SPACE) + "Content.step: mapIterator - " + next.iteratorToString());

            if (mapIterator != null){
                out(var(SPACE) + "Content.step: return - " + mapIterator);
                return true;
            }
        }

        out(var(SPACE) + "Content.step: return - NULL");
        return false;
    }

    @Override
    public boolean hasStep() {
        out(var(SPACE) + "Content.hasStep = " + iterator.hasNext());
        return iterator.hasNext();
    }

    @Override
    public boolean hasData(){
        out(var(SPACE) + "Content.hasData = " + (next != null));
        return next != null;
    }

    @Override
    public String last() {
        out(var(SPACE) + "Content.last = " + lastVal);
        return String.valueOf(lastVal);
    }

    @Override
    public int iteration() {
        out(var(SPACE) + "Content.iteration = " + iteration);
        return iteration;
    }

    @Override
    public ContentAPI getParent() {
        out(var(SPACE) + "Content.parent = " + parent);
        return parent;
    }

}
